#pragma once

#include <iostream>
#include <sstream>
#include <deque>
#include <list>
#include <map>
#include <set>
#include <string>
#include <vector>

namespace Zen
{
    /**

 usage:
 #1
 Printer{char(0x20)}(1, 2, 3, 4, "hello");

 #2
 Printer{}(1, 2, 3, 4, "hello");

 #3
 Printer{std::string(1, 0x20)}(1, 2, 3, 4, "hello");

 #4
 std::stringstream ss;
 Printer{" ", "\n", ss}(1, 2, 3, 4, "hello");
 std::string str;
 ss >> str;
 mOS << str << std::endl;

 */

    class CharStream
    {
      public:
        CharStream(std::basic_ostream<char>& bos)
          : mOS(bos)
        {
        }

        template <typename Type>
        void Push(Type& t)
        {
            PushItem(t);
        }

        template <typename Value, typename Alloc>
        void Push(std::deque<Value, Alloc> const& v)
        {
            PushGroup(v, '[', ']');
        }

        template <typename Key, typename Cmp, typename Alloc>
        void Push(std::set<Key, Cmp, Alloc> const& v)
        {
            PushGroup(v, '[', ']');
        }

        template <typename Key, typename Value, typename Cmp, typename Alloc>
        void Push(std::map<Key, Value, Cmp, Alloc> const& v)
        {
            PushGroup(v, '{', '}');
        }

        template <typename Value, typename Alloc>
        void Push(std::vector<Value, Alloc> const& v)
        {
            PushGroup(v, '[', ']');
        }

        template <class Value, size_t Size>
        void Push(std::array<Value, Size> const& v)
        {
            PushGroup(v, '[', ']');
        }

        template <size_t Size>
        void Push(char const (&v)[Size])
        {
            PushItem((char const*)v);
        }

        template <class Value, size_t Size>
        void Push(Value const (&v)[Size])
        {
            PushGroup(v, '[', ']');
        }

        template <typename Type>
        void Push(std::initializer_list<Type> const& v)
        {
            PushGroup(v, '[', ']');
        }

      private:
        template <typename T>
        void PushGroup(T const& v, char Lq, char Rq)
        {
            mOS << Lq;
            auto iter = std::begin(v);
            if (iter != std::end(v))
            {
                Push(*iter);
                for (++iter; iter != std::end(v); ++iter)
                {
                    PushItem(',');
                    Push(*iter);
                }
            }
            mOS << Rq;
        }

      private:
        template <typename Key, typename Value>
        void PushItem(std::pair<Key, Value> const& v)
        {
            Push(v.first);
            PushItem(':');
            Push(v.second);
        }
        template <typename T>
        void PushItem(const T& v)
        {
            mOS << v;
        }
        void PushItem(char c)
        {
            if (c) mOS << c;
        }

      private:
        std::basic_ostream<char>& mOS;
    };

    template <typename Type = char>
    class Printer
    {
      private:
        CharStream mStr;
        Type mSeperator;
        Type mEndline;

      public:
        Printer(Type const& seperator, Type const& endline, std::basic_ostream<char>& os = std::cout)
          : mStr(os)
        {
            mSeperator = seperator;
            mEndline = endline;
        }

        template <typename Type, typename... Ts>
        void Print(Type const& v, Ts const&... s)
        {
            mStr.Push(v);
            mStr.Push(mSeperator);
            Print(s...);
        }

        template <typename Type>
        void Print(Type const& v)
        {
            mStr.Push(v);
            mStr.Push(mEndline);
        }

        template <typename... Ts>
        void operator()(Ts const&... vs)
        {
            Print(vs...);
        }
    };

    template <typename... Ts>
    inline std::string ToString(Ts const&... vs)
    {
        std::stringstream ss;
        Printer<char>{ ',', 0, ss }(vs...);
        return ss.str();
    }
} // namespace Zen

#define PRINTER Zen::Printer<char>(0x20, 10)
